/**
 * Provides ability to convert a given craigslist HTML page to structured data (XML, JavaScript Array, etc).  Basically scrapes
 * the content and organizes into useful structures.  Capable of understanding posting, index, and search results pages.
 * Also provides support for converting a JSON-fomatted RSS feed routed through Yahoo!pipes into a CDF document.
 * @author birdeau@yahoo.com
 */
jsx3.lang.Class.defineClass("gicx.Filter", null, [], function(Filter,Filter_prototype) {

  //regular expressions used to parse the Craigslist content
  var myListReg = /<p>(?:&nbsp;([^<]*)&nbsp;&nbsp;&nbsp;)?<a href="([^"]*)"[^>]*>([^<]*)<\/a>[\s]*(?:<font[^>]*>[\s]*\(([^<]*)\)<\/font>)?[\s]*(?:<span[^>]*>[\s]*(pic)[^<]*<\/span>)?/ig;
  var myNormalizerReg = /(href|src)=([^"])([^\s\/>]*)(\s|>|\/)/gi;
  var myTargetReg = /href="([^"]*)"/i;
  var myImgReg = /<img[^>]*src="([^"]*\.jpg)"/gi;
  var myBodyReg = /<body[^>]*>([\s\S]*?)(<h2[\s\S]*)<\/body>/i;
  var myCountReg = /<b>Found:[\s]*([\d]*)[\s]*Displaying:[\s]*([,\d]*)[\s]*-[\s]*([,\d]*)<\/b>/i;
  var myEmailReg = /([a-zA-Z0-9_\.+-])+@(([a-zA-Z0-9-])+\.)+([a-zA-Z0-9]{2,6})/g;
  var myPhoneReg = /(?:[\(]?([0-9]{3})[\)]?[\.| |\-]{0,1}|^[0-9]{3}[\.|\-| ]?)?([0-9]{3})(?:\.|\-| )?([0-9]{4})(( |-|\.)?([ext\.]+ ?\d+))?\b/gi;
  var myPriceReg = /\$(\d*)/;
  var myUrlStripperReg = /http[s]?:[^\s]*/g;
  var myTagStripperReg = /<[^>]*\/?>/g;
  var myEntityReg = /&#(\d*);/gi;
  var myUniStripperReg = /\S?/g;
  var myIndexPageCountReg = /next 100 postings/i;
  var myDateReg = /([^<]*)<\/h4>/i;
  var myGeoReg = /href="http:\/\/maps.google.com\/\?q=loc%3A([^>]*)">/;
  var myRSSLocReg = /(?:\()([^\(^\)]*)(?:\))/;
  var myRSSPriceReg = /(?:\$)(\d*)/;


  /**
   * main method
   */
  Filter_prototype.init = function(strHTML) {
    this.jsxsuper();
    this._html = strHTML;
  };


  /**
   * Returns the innerHTML content of the document body.  Allows the given page from CL to be inlined in the existing web page without need for an IFRAME container.
   * Ensures all hyperlinks in the posting are modified, so they open a new browser window when clicked (target = new)
   * @returns {Array} [header,content]
   */
  Filter_prototype.getBody = function() {
    myBodyReg.exec(this._html);
    var str = RegExp.$1;
    var str2 = RegExp.$2;
    //jsx3.log(str);
    return (str) ? [str.replace(/<a href="\//gi,'<a href="http://www.craigslist.org/').replace(/<a href=/gi,'<a target="_new" href='),str2.replace(/<a href="\//gi,'<a href="http://www.craigslist.org/').replace(/<a href=/gi,'<a target="_new" href=').replace(/<form action="http:\/\/flag/gi,'<form target="_new" action="http://flag')] : null;
  };


  /**
   * Returns an array of URLs for all images on the page. Currently only jpg images are returned.
   * @returns {Array<String>}
   */
  Filter_prototype.getImages = function() {
    //normalize the HTML before scanning for images
    var strHTML = this._html;
    strHTML = strHTML.replace(myNormalizerReg,'$1="$2$3"$4');
    var a = [];
    while(myImgReg.exec(strHTML))
      a.push(RegExp.$1);
    return a;
  };


  /**
   * Returns a single address as set by the poster (a valid google address)
   * @returns {String || null}
   */
  Filter_prototype.getAddress = function() {
    if(typeof(this._html) == "string") {
      this._html.search(myGeoReg);
      var s = RegExp.$1;
      return (jsx3.util.strEmpty(s) || s.length < 5) ? null : s;
    }
  };


  /**
   * Returns an array of emails numbers in a given posting. Never returns more than 4
   * @returns {Array<String>}
   */
  Filter_prototype.getEmails = function() {
    //strip the HTML tags, so only the innerText property exists; also expand entities (craigslist escapes the characters--probably to hide from malicious page scanners)
    var strHTML = this._html;
    strHTML = strHTML.replace(myUniStripperReg,function(a) {return a.charCodeAt(0) > 128 ? " " : a;}).replace(myTagStripperReg," ").replace(myEntityReg,function($1,$2){return String.fromCharCode($2);}).replace(myUrlStripperReg,"").replace(/([\s]*)\s/g," ");
    var a = [];
    var arr;
    var i = 0;
    while((arr = myEmailReg.exec(strHTML)) != null && i<4) {
      a.push([arr[0]]);
      i++;
    }
    return a;
  };


  /**
   * Returns an array of phone/fax numbers in a given posting.
   * @returns {Array<String>}
   */
  Filter_prototype.getPhones = function() {
    //strip the HTML tags, so only the innerText property exists
    var strHTML = this._html;
    strHTML = strHTML.replace(myUniStripperReg,function(a) {return a.charCodeAt(0) > 128 ? " " : a;}).replace(myTagStripperReg," ").replace(myEntityReg,function($1,$2){return String.fromCharCode($2);}).replace(myUrlStripperReg,"").replace(/([\s]*)\s/g," ");
    var a = [];
    var arr;
    var i = 0;
    while((arr = myPhoneReg.exec(strHTML)) != null && i<5) {
      a.push(((RegExp.$1)?"("+RegExp.$1+") ":"") + RegExp.$2 + "-" + RegExp.$3);
      i++;
    }
    return a;
  };


  /**
   * Returns a CDF document of result items, with named record attributes (jsxid, jsxtext, loc, pic);
   * Those postings that are in the 'blocked' list will not be added (even though returned from craigslist)
   * @param objDoc {jsx3.xml.Document} if passed, the records will be added to this document; otherwise a new document will be created
   * @returns {jsx3.xml.CDF.Document}
   */
  Filter_prototype.getPostings = function(objDoc) {
    //convert Craigslist list format to CDF format
    var vntResponse = this._html;
    var objDoc = (objDoc instanceof jsx3.xml.Document) ? objDoc : jsx3.xml.CDF.Document.newDocument();

    //the filter class will handle/convert both RSS (as JSON via yahoo pipes) or a standard search results page (HTML) returned directly from craigslist
    if(typeof(vntResponse) == "string") {
      //h4 delimiters are used to tag dates on all pages rendered in 'list server' mode. split on this in order to get a date for those following
      //records that do not contain a date
      var objHTML = vntResponse.split("<h4>");
      for(var i=0;i<objHTML.length;i++) {
        var vntResponse = objHTML[i];
        myDateReg.exec(vntResponse);
        var s1 = RegExp.$1;

        while(myListReg.exec(vntResponse)) {
          var s2 = RegExp.$1;
          sDate = jsx3.util.strEmpty(s2) ? (s1 || "") : s2;
          var s3 = RegExp.$5;
          s3 = jsx3.util.strEmpty(s3) ? "" : "pic";
          var o = {jsxid:RegExp.$2,jsxtext:RegExp.$3,loc:RegExp.$4,pic:s3,date:sDate};
          //check the permanant and temp caches for the posting; skip the iteration if the given posting should be blocked
          var objNode = gicx.Cache.getRecord("searchHistory",o.jsxid);
          if(objNode && objNode.getParent().getNodeName() == "blocked") {
            jsx3.log("Did not load the blocked posting: " + o.jsxid);
            continue;
          } else if(!objNode) {
            objNode =  gicx.Cache.getRecord("_searchHistory",o.jsxid);
          }

          if(objNode) {
            var objClone = objNode.cloneNode(true);
            objClone.removeAttribute("jsxselected");
            objNode = objDoc.insertRecordNode(objClone);
          } else {
            myPriceReg.exec(o.jsxtext);
            if(!jsx3.util.strEmpty(RegExp.$1))
              o.price = RegExp.$1;
            objNode = objDoc.insertRecord(o);
          }
        }
      }
    } else {
      //filter the response (json-formatted rss)
      if(vntResponse && vntResponse.value && vntResponse.value.items && vntResponse.value.items.length) {
        for(var i=0;i<vntResponse.value.items.length;i++) {
          var curItem = vntResponse.value.items[i];
          var o = {};
          o.jsxid = curItem.link
          o.jsxtext = curItem.title
          o.content = curItem.description;
          o.pic = "";
          var objDate = (curItem.pubDate+"").split("T");
          if(objDate.length == 2) {
            objDate = objDate[0].split("-");
            if(objDate.length == 3)
              o.date = objDate[1] + "-" + objDate[2];
          }
          var sPrice = myRSSPriceReg.exec(o.jsxtext);
          if(!jsx3.util.strEmpty(sPrice))
            o.price = sPrice[1];
            //jsx3.log(sPrice);
          var sLoc = myRSSLocReg.exec(o.jsxtext);
          if(!jsx3.util.strEmpty(sLoc))
            o.loc = sLoc[1];
          objDoc.insertRecord(o);
        }
      }
    }
    return objDoc;
  };


  /**
   * Returns the total number of search results for a given query. This is different from
   * the number of result items returned in the given page and instead is the count of
   * all items on all pages.
   * @returns {Object} with named properties: start, end, total,index (if index, this means this is an index page, not a search, so the 'viewing...' should adjust
   */
  Filter_prototype.getRange = function() {
    //var objRange = {start:1,end:i+1,total:i+1}
    var strHTML = this._html;
    if(myCountReg.exec(strHTML)) {
      //page with results
      var s1 = (RegExp.$1) ? RegExp.$1 : '0';
      var s2 = (RegExp.$2) ? RegExp.$2 : '0';
      var s3 = (RegExp.$3) ? RegExp.$3 : '0';
      return {start:parseInt(s2),end:parseInt(s3),total:parseInt(s1),index:false};
    } else if(myIndexPageCountReg.exec(strHTML)) {
      //indexed page (the list server)
      var s1 = (RegExp.$1) ? RegExp.$1 : '0';
      return {start:1,end:100,total:0,index:true};
    } else {
      //no results
      return {start:0,end:0,total:0,index:false};
    }
  };

});



